#!/usr/bin/bash
pro_base="$(cd `dirname $(readlink -f $0)`;pwd)"
    # program base directory. readlink是为了获取脚本的真实路径
pro_root="${pro_base##*/}"
    # 程序的根目录(脚本的父目录). readlink使得ln创建的软链也能正确地执行
pro_name="$(readlink -f $0 | sed 's#^.*/##; s#.sh$##')"
    # sed命令: 1.删除'/'前的路径 2.删除'.sh'
temp_log=/tmp/${pro_name}.log
    # temp_log往往用于需要后台执行的程序,比如通过crontab执行的程序
today="$(date +%Y%m%d)"
log_dir="/var/log/${pro_root}/${pro_name}"
stdout_log="${log_dir}/${pro_name}.${today}.log"

exclude_pattern_file=${pro_base}/${pro_name}/${pro_name}.exclude
source_file=${pro_base}/${pro_name}/${pro_name}.source
target_host_list=${pro_base}/${pro_name}/${pro_name}.target
backup_status=0

Source(){
    source ${pro_base}/shared/include.sh || exit 1
    Include ${pro_base}/${pro_name}/{usage.sh,${pro_name}.conf}
    Include ${pro_base}/shared/{prompt.sh,mail.sh}
}

Check_condition(){
    Help "$@"
    [[ $# -ne 1 ]] && Usage && exit 1
    [[ -d "${log_dir}" ]] || Eval_exit "mkdir -p '${log_dir}'"
    if [[ ${mail_wanted} == 0 ]]; then
        # 重新定义 MailToAny()
        MailToAny(){ PNote "变量\$mail_wanted == 0,不发邮件.邮件主题: $2"; }
    fi
    ! [[ -f ${target_host_list} ]] && PError "[缺失'主机清单'文件]: ${target_host_list},退出程序" && ExitUnusual
    ! [[ -f ${exclude_pattern_file} ]] && PError "[缺失'排除pattern'文件]: ${exclude_pattern_file},退出程序" && ExitUnusual
    ! [[ -f ${source_file} ]] && PError "[缺失'源文件清单']: ${source_file},退出程序" && ExitUnusual
}

ExitUnusual(){
    MailToAny "${mailToAny}" "[ WARN ] ${source_host}: 有些数据同步失败" "$(Change_to_html ${stdout_log})"
    exit 1
}

Rsync_backup()
{
    Eval_exit "Backup_core"
    PInfo "数据已同步,发邮件通知管理员"
    if [[ ${backup_status} == 0 ]]; then
        MailToAny "${mailToAny}" "[  OK  ] ${source_host}: 全部数据同步成功" "$(Change_to_html ${stdout_log})"
    else
        MailToAny "${mailToAny}" "[ WARN ] ${source_host}: 有些数据同步失败" "$(Change_to_html ${stdout_log})"
    fi
}

Backup_core(){
    local i j h p path days backup_dir is_local rsync_s rsync_t source_f
    for i in $(egrep -av '^#|^$' ${target_host_list})
    do
        if [[ `echo "$i" |awk -F',' '{print NF}'` != 3 ]]; then
            PError "['主机清单'文件]: ${target_host_list}, 下面的内容格式错误\n\t${i}"
            ExitUnusual
        fi
    done

    is_local=$(awk -v sh=${source_host} '/127\.0\.0\.1/{r=0;for(i=1;i<=NF;i++){ if($i==sh){r=1}}}END{print r}' /etc/hosts)  # 1是localhost;0不是localhost
    if [[ ${is_local} == 1 ]]; then
        rsync_s="/"
    else
        rsync_s="${source_host}:/"
        source_f=":"                    # --files-from选项的使用方法: 从远端复制到本地,并不同于从本地复制到远端
    fi
    
    for j in $(egrep -av '^#|^$' ${target_host_list})
    do
        h="$(echo "$j" |awk -F',' '{print $1}')"
            # h: host
        is_local=$(awk -v sh=${h} '/127\.0\.0\.1/{r=0;for(i=1;i<=NF;i++){ if($i==sh){r=1}}}END{print r}' /etc/hosts)  # 1是localhost;0不是localhost
        [[ ${is_local} == 1 ]] && rsync_t="" || rsync_t="${h}:"
        
        p="$(echo "$j" |awk -F',' '{print $2}')"
            # p: port
        path="$(echo "$j" |awk -F',' '{print $3}')"
        days=$(ssh -p $p $h "mkdir -p ${path}; ls -1 ${path} |grep -c '${source_host}_'")   # $source_host 或 $h 不会同时都是远程主机
            # days: 已有的"增量备份"文件夹的数量
        backup_dir=${path}/${source_host}_$(date -d'-1 day' +%F)
        if [[ ${days} -ge ${keep_days} ]]; then
            ! ssh -p $p $h "path=${path};source_host=${source_host};"'oldest=$(ls -dtr1 ${path}/${source_host}_* |head -n1); chmod -R u+w ${oldest}; rm -rf ${oldest}' && 
                PError "无法删除最老的增量备份目录,退出程序" && ExitUnusual         # /root目录没有删除权限,若备份过/root目录,就要chmod +w,否则报错
        fi                                                                      # 若备份至windows共享文件夹,执行chmod 777后,再执行stat或ls -l并不能正确显示权限
        
        if rsync -arR --del --exclude-from="${exclude_pattern_file}" --files-from="${source_f}${source_file}" -e"ssh -p ${p}" \
                -b --backup-dir=${backup_dir} "${rsync_s}" "${rsync_t}"${path}/${source_host}.latest; then
            POk "源文件清单: ${source_file} 中的文件已成功备份到'${h}:${path}'"
        else
            ((backup_status++))
            PError "源文件清单: ${source_file} 中的文件没有全部成功备份到'${h}:${path}'"
        fi
        Eval_cont "ssh -p $p $h 'ls -lt ${path}'"
        echo -e '\n******************** 分割线 ********************\n'
    done
}

Set_cron(){
    local minute=$(date +%M)
    if ! Eval_cont "type send_mail.pl"; then
        Eval_exit "chmod +x ${pro_base}/shared/send_mail.pl"
        Eval_exit "ln -sf ${pro_base}/shared/send_mail.pl /usr/bin"
    fi

    if grep -a "bash ${pro_base}/${pro_name}.sh -b" /var/spool/cron/root; then
        PInfo "已存在计划任务: [相关任务] ${pro_name}.sh"
    else
        PInfo "不存在计划任务: [安装任务] ${pro_name}.sh"
        echo "$minute 3 * * * bash ${pro_base}/${pro_name}.sh -b" >> /var/spool/cron/root
    fi
    Eval_cont "crontab -l"
}

case $1 in
    -b)
        {
        Source
        Check_condition "$@"
        } &> ${temp_log}
        rm -f ${temp_log}
        
        Rsync_backup &>> ${stdout_log}
        # 复制失败的文件会被记录到${stdout_log}
    ;;
    -f)
        Source
        Check_condition "$@"
        
        Rsync_backup |& tee -a ${stdout_log}
    ;;
    -s)
        Source
        Check_condition "$@"
        Set_cron
    ;;
    *)
        Source
        Usage && exit 1
    ;;
esac
